/*
 * Created on Mar 7, 2005
 * Created by dfhuynh
 */
package edu.mit.simile.longwell.tag;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.log4j.Logger;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.vocabulary.RDFS;

import edu.mit.simile.longwell.Cache;
import edu.mit.simile.longwell.Profile;
import edu.mit.simile.longwell.StructuredModelBase;

/**
 * @author dfhuynh
 */
public class TagModel extends StructuredModelBase implements ITagModel {
	final static private Logger s_logger = Logger.getLogger(TagModel.class);
	
	static protected String s_idForGeneratingURIs;
	static protected String s_tagSuffixHash;
	
	static public void setIDForGeneratingURIs(String id) {
		s_idForGeneratingURIs = id != null ? id : "unknown";
		s_tagSuffixHash = md5hash(id);
	}
	
	static public String getIDForGeneratingURIs() {
		return s_idForGeneratingURIs;
	}
	
	protected boolean m_initialized;
	
	final protected Map 		m_tagToLabels = new HashMap();
	final protected Map 		m_labelToTags = new HashMap();
	final protected SortedSet	m_cachedTagLabels = new TreeSet(new Comparator() {
		public boolean equals(Object obj) {
			return false;
		}

		public int compare(Object o1, Object o2) {
			String s1 = (String) o1;
			String s2 = (String) o2;
			int i = s1.compareToIgnoreCase(s2);
			if (i == 0) {
				i = s1.compareTo(s2);
			}
			return i;
		}
	});
		
	final protected Cache	m_tagToObjects = new Cache(100, TagModel.class, "tag-to-objects");
	final protected Cache	m_labelToObjects = new Cache(100, TagModel.class, "label-to-objects");
	final protected Cache	m_objectToTags = new Cache(100, TagModel.class, "object-to-tags");
	final protected Cache	m_objectToTagLabels = new Cache(100, TagModel.class, "object-to-tag-labels");
	final protected Cache	m_objectsToTags = new Cache(100, TagModel.class, "objects-to-tags");
	final protected Cache	m_objectsToTagLabels = new Cache(100, TagModel.class, "objects-to-tag-labels");
	
	/**
	 * @param profile
	 * @param dir
	 */
	public TagModel(Profile profile, File dir) {
		super(profile, dir);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IStructuredModel#dispose()
	 */
	synchronized public void dispose() {
		if (m_initialized) {
			try {
				saveToDir();
			} catch (Exception e) {
				s_logger.warn(e);
			}
		}
		super.dispose();
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getTags()
	 */
	synchronized public Set getTags() {
		internalInitialize();
		return new HashSet(m_tagToLabels.keySet());
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getTagLabels()
	 */
	synchronized public Set getTagLabels() {
		internalInitialize();
		return new HashSet(m_cachedTagLabels);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getTagLabel(com.hp.hpl.jena.rdf.model.Resource)
	 */
	synchronized public String getTagLabel(Resource tag) {
		internalInitialize();
		
		Set labels = new HashSet();
		
		tagToLabels(tag, labels);

		return labels.size() > 0 ? (String) labels.iterator().next() : "";
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getObjects(com.hp.hpl.jena.rdf.model.Resource)
	 */
	synchronized public Set getObjects(Resource tag) {
		internalInitialize();
		
		Set objects = (Set) m_tagToObjects.get(tag);
		if (objects == null) {
			objects = internalGetObjects(tag);
			m_tagToObjects.put(tag, objects);
		}
		return objects;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getObjects(java.lang.String)
	 */
	synchronized public Set getObjects(String tagLabel) {
		internalInitialize();
		
		Set objects = (Set) m_labelToObjects.get(tagLabel);
		if (objects == null) {
			Set tags = new HashSet();
			
			labelToTags(tagLabel, tags, false);
			
			objects = new HashSet();
			
			Iterator i = tags.iterator();
			while (i.hasNext()) {
				Resource tag = (Resource) i.next();
				
				objects.addAll(getObjects(tag));
			}

			m_labelToObjects.put(tagLabel, objects);
		}
		return objects;
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getObjectTags(com.hp.hpl.jena.rdf.model.Resource)
	 */
	synchronized public Set getObjectTags(Resource object) {
		internalInitialize();

		Set tags = (Set) m_objectToTags.get(object);
		if (tags == null) {
			tags = internalGetTags(object);
			m_objectToTags.put(object, tags);
		}
		return tags;
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getObjectTags(java.util.Set)
	 */
	synchronized public Set getObjectTags(Set objects) {
		internalInitialize();

		Set tags = (Set) m_objectsToTags.get(objects);
		if (tags == null) {
			tags = new HashSet();
			
			Iterator i = objects.iterator();
			while (i.hasNext()) {
				Set tags2 = getObjectTags((Resource) i.next());
				
				tags.addAll(tags2);
			}

			m_objectsToTags.put(objects, tags);
		}
		return tags;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getObjectTagLabels(com.hp.hpl.jena.rdf.model.Resource)
	 */
	synchronized public Set getObjectTagLabels(Resource object) {
		internalInitialize();

		Set tagLabels = (Set) m_objectToTagLabels.get(object);
		if (tagLabels == null) {
			tagLabels = internalGetTagLabels(object);
			m_objectToTagLabels.put(object, tagLabels);
		}
		return tagLabels;
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getObjectTags(java.util.Set)
	 */
	synchronized public Set getObjectTagLabels(Set objects) {
		internalInitialize();

		Set tagLabels = (Set) m_objectsToTagLabels.get(objects);
		if (tagLabels == null) {
			tagLabels = new HashSet();
			
			Iterator i = objects.iterator();
			while (i.hasNext()) {
				Set tagLabels2 = getObjectTagLabels((Resource) i.next());
				
				tagLabels.addAll(tagLabels2);
			}

			m_objectsToTagLabels.put(objects, tagLabels);
		}
		return tagLabels;
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#getObjectTagString(com.hp.hpl.jena.rdf.model.Resource)
	 */
	synchronized public String getObjectTagString(Resource object) {
		SortedSet tags = new TreeSet(getObjectTagLabels(object));
		String separator = "";
		String s = "";
		
		Iterator i = tags.iterator();
		while (i.hasNext()) {
			s = s + separator + i.next();
			separator = ", ";
		}

		return s;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#completeTags(java.lang.String, int)
	 */
	synchronized public List completeTags(String prefix, int max) {
		internalInitialize();
		
		List tags = new ArrayList();
		
		prefix = prefix.toLowerCase();
		
		SortedSet tailSet = m_cachedTagLabels.tailSet(prefix);
		Iterator i = tailSet.iterator();
		while (i.hasNext() && max > 0) {
			String tag = (String) i.next();
			if (tag.toLowerCase().startsWith(prefix)) {
				tags.add(tag);
				max--;
			} else {
				break;
			}
		}

		return tags;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#clearTags(com.hp.hpl.jena.rdf.model.Resource)
	 */
	synchronized public void clearTags(Resource object) {
		Model model = m_profile.getModel();
		Model model2 = ModelFactory.createDefaultModel();
		Property tagProperty = model.createProperty(s_simile_tag);
		
		model2.add(
			model.listStatements(
				object, tagProperty, (RDFNode) null));
		
		m_profile.removeData(model2);
		model2.close();
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#clearTags(java.util.Set)
	 */
	synchronized public void clearTags(Set objects) {
		Model model = m_profile.getModel();
		Model model2 = ModelFactory.createDefaultModel();
		Property tagProperty = model.createProperty(s_simile_tag);
		
		Iterator i = objects.iterator();
		while (i.hasNext()) {
			Resource object = (Resource) i.next();
			
			model2.add(
				model.listStatements(
					object, tagProperty, (RDFNode) null));
		}
		
		m_profile.removeData(model2);
		model2.close();
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#assignTags(com.hp.hpl.jena.rdf.model.Resource, java.lang.String[])
	 */
	synchronized public void assignTags(Resource object, String[] tagLabels) {
		Model model = m_profile.getModel();
		Model model2 = ModelFactory.createDefaultModel();
		Property tagProperty = model.createProperty(s_simile_tag);

		Set tags = new HashSet();
		for (int i = 0; i < tagLabels.length; i++) {
			labelToTags(tagLabels[i].trim(), tags, true);
		}
		
		Iterator i = tags.iterator();
		while (i.hasNext()) {
			Resource tag = (Resource) i.next();
			model2.add(object, tagProperty, tag);
		}
		
		m_profile.addData(model2);
		model2.close();
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.tag.ITagModel#assignTags(java.util.Set, java.lang.String[])
	 */
	synchronized public void assignTags(Set objects, String[] tagLabels) {
		Model model = m_profile.getModel();
		Model model2 = ModelFactory.createDefaultModel();
		Property tagProperty = model.createProperty(s_simile_tag);
		
		Set tags = new HashSet();
		for (int i = 0; i < tagLabels.length; i++) {
			labelToTags(tagLabels[i].trim(), tags, true);
		}
		
		Iterator j = objects.iterator();
		while (j.hasNext()) {
			Resource object = (Resource) j.next();
			
			Iterator i = tags.iterator();
			while (i.hasNext()) {
				Resource tag = (Resource) i.next();
				model2.add(object, tagProperty, tag);
			}
		}
		
		m_profile.addData(model2);
		model2.close();
	}

	protected void internalInitialize() {
		if (m_initialized) {
			return;
		}
		
		if (m_dir.exists()) {
			try {
				loadFromDir();
			} catch (Exception e) {
				s_logger.error(e);
				
				m_labelToTags.clear();
				m_tagToLabels.clear();
				m_cachedTagLabels.clear();
				
				internalOnAfterAdd(m_profile.getModel());
			}
		} else {
			m_dir.mkdirs();
			
			internalOnAfterAdd(m_profile.getModel());
		}
		
		m_initialized = true;
	}
	
	protected void loadFromDir() throws Exception {
		File file = new File(m_dir, "tags");
		if (file.exists()) {
			FileInputStream fis = null;
			try {
				fis = new FileInputStream(file);
				
				ObjectInputStream ois = new ObjectInputStream(fis);
				Model model = m_profile.getModel();
				
				{
					int tagCount = ois.readInt();
					for (int i = 0; i < tagCount; i++) {
						String tagURI = (String) ois.readObject();
						Resource tag = model.createResource(tagURI);
						
						int labelCount = ois.readInt();
						if (labelCount == 1) {
							String label = (String) ois.readObject();

							m_tagToLabels.put(tag, label);
						} else {
							Set labels = new HashSet();
							
							for (int j = 0; j < labelCount; j++) {
								String label = (String) ois.readObject();
								
								labels.add(label);
							}
							m_tagToLabels.put(tag, labels);
						}
					}
				}
				{
					int labelCount = ois.readInt();
					for (int i = 0; i < labelCount; i++) {
						String label = (String) ois.readObject();
						
						m_cachedTagLabels.add(label);
						
						int tagURICount = ois.readInt();
						if (tagURICount == 1) {
							String tagURI = (String) ois.readObject();

							m_labelToTags.put(label, model.createResource(tagURI));
						} else {
							Set tags = new HashSet();
							
							for (int j = 0; j < tagURICount; j++) {
								String tagURI = (String) ois.readObject();
								
								tags.add(model.createResource(tagURI));
							}
							m_tagToLabels.put(label, tags);
						}
					}
				}
			} finally {
				if (fis != null) {
					fis.close();
				}
			}
		}
	}
	
	protected void saveToDir() throws Exception {
		File file = new File(m_dir, "tags");
		
		FileOutputStream fos = null;
		ObjectOutputStream oos = null;
		try {
			fos = new FileOutputStream(file);
			oos = new ObjectOutputStream(fos);
			{
				Set tags = new HashSet(m_tagToLabels.keySet());
				
				oos.writeInt(tags.size());
				
				Iterator t = tags.iterator();
				while (t.hasNext()) {
					Resource tag = (Resource) t.next();
					
					oos.writeObject(tag.getURI());
					
					Object o = m_tagToLabels.get(tag);
					if (o instanceof String) {
						oos.writeInt(1);
						oos.writeObject((String) o);
					} else if (o instanceof Set) {
						Set labels = (Set) o;
						
						oos.writeInt(labels.size());
						
						Iterator l = labels.iterator();
						while (l.hasNext()) {
							oos.writeObject((String) l.next());
						}
					} else {
						oos.writeInt(0);
					}
				}
			}
			{
				Set labels = m_labelToTags.keySet();
				
				oos.writeInt(labels.size());
				
				Iterator l = labels.iterator();
				while (l.hasNext()) {
					String label = (String) l.next();
					
					oos.writeObject(label);
					
					Object o = m_labelToTags.get(label);
					if (o instanceof Resource) {
						oos.writeInt(1);
						oos.writeObject(((Resource) o).getURI());
					} else if (o instanceof Set) {
						Set tags = (Set) o;
						
						oos.writeInt(tags.size());
						
						Iterator t = tags.iterator();
						while (t.hasNext()) {
							oos.writeObject(((Resource) t.next()).getURI());
						}
					} else {
						oos.writeInt(0);
					}
				}
			}
		} finally {
			if (oos != null) {
				oos.close();
			}
			if (fos != null) {
				fos.close();
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onAfterAdd(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onAfterAdd(Model model) {
		internalInitialize();
		internalOnAfterAdd(model);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onAfterRemove(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onAfterRemove(Model model) {
		internalInitialize();
		internalOnAfterRemove(model);
	}
	
	protected void internalOnAfterAdd(Model model) {
		// Update existing tag labels
		{
			ResIterator ri = model.listSubjectsWithProperty(RDFS.label);
			if (ri.hasNext()) {
				m_labelToObjects.clear();
			}
			
			while (ri.hasNext()) {
				Resource r = ri.nextResource();
				if (m_tagToLabels.containsKey(r)) {
					String derivedLabel = deriveTagLabel(r);
					Set labels = new HashSet();
					
					Object o = m_tagToLabels.get(r);
					if (o instanceof String) {
						labels.add(o);
					} else if (o instanceof Set) {
						labels.addAll((Set) o);
					}
					
					labels.remove(derivedLabel);
					
					NodeIterator ni = model.listObjectsOfProperty(r, RDFS.label);
					while (ni.hasNext()) {
						RDFNode node = ni.nextNode();
						if (node instanceof Literal) {
							String label = ((Literal) node).getString();
							
							labels.add(label);
							
							enterLabelForTag(label, r);
						}
					}
					
					if (labels.size() == 1) {
						m_tagToLabels.put(r, labels.iterator().next());
					} else {
						m_tagToLabels.put(r, labels);
					}
				}
			}
		}

		// Update associations between tags and objects and detect new tags
		{
			Property tagProperty = model.createProperty(s_simile_tag);
			NodeIterator ni = model.listObjectsOfProperty(tagProperty);
			if (ni.hasNext()) {
				m_objectsToTags.clear();
				m_objectsToTagLabels.clear();
				m_labelToObjects.clear();
			}
			
			while (ni.hasNext()) {
				RDFNode node = ni.nextNode();
				if (node instanceof Literal) {
					continue;
				}
	
				Resource tag = (Resource) node;
				if (tag.isAnon()) {
					continue;
				}
				
				if (!m_tagToLabels.containsKey(tag)) {
					onNewTag(tag, model);
				}
				
				Set objects = (Set) m_tagToObjects.get(tag);
				if (objects != null) {
					ResIterator ri = model.listSubjectsWithProperty(tagProperty, tag);
					while (ri.hasNext()) {
						objects.add(ri.nextResource());
					}
					
					// Caching strategy: set changes, create new set
					m_tagToObjects.put(tag, new HashSet(objects));
				}
				
				ResIterator ri = model.listSubjectsWithProperty(tagProperty, tag);
				while (ri.hasNext()) {
					Resource r = ri.nextResource();
					
					Set tags = (Set) m_objectToTags.get(r);
					if (tags != null) {
						tags.add(tag);
						// Caching strategy: set changes, create new set
						m_objectToTags.put(r, new HashSet(tags));
					}
					Set tagLabels = (Set) m_objectToTagLabels.get(r);
					if (tagLabels != null) {
						tagToLabels(tag, tagLabels);
						// Caching strategy: set changes, create new set
						m_objectToTagLabels.put(r, new HashSet(tagLabels));
					}
				}
			}
		}
	}
	
	protected void onNewTag(Resource tag, Model model) {
		Model model2 = m_profile.getModel();
		Set labels = new HashSet();
		
		NodeIterator ni2 = model2.listObjectsOfProperty(tag, RDFS.label);
		while (ni2.hasNext()) {
			RDFNode node2 = ni2.nextNode();
			if (node2 instanceof Literal) {
				String label = ((Literal) node2).getString();
				
				labels.add(label);
				
				enterLabelForTag(label, tag);
			}
		}
		
		if (labels.size() == 1) {
			m_tagToLabels.put(tag, labels.iterator().next());
		} else if (labels.size() > 0) {
			m_tagToLabels.put(tag, labels);
		} else {
			String label = deriveTagLabel(tag);
			
			m_tagToLabels.put(tag, label);
			enterLabelForTag(label, tag);
		}
	}
	
	protected void internalOnAfterRemove(Model model) {
		// Update tag labels
		{
			ResIterator ri = model.listSubjectsWithProperty(RDFS.label);
			if (ri.hasNext()) {
				m_labelToObjects.clear();
			}
			
			while (ri.hasNext()) {
				Resource r = ri.nextResource();
				if (m_tagToLabels.containsKey(r)) {
					String derivedLabel = deriveTagLabel(r);
					Set labels = new HashSet();
					
					Object o = m_tagToLabels.get(r);
					if (o instanceof String) {
						labels.add(o);
					} else if (o instanceof Set) {
						labels.addAll((Set) o);
					}
					
					labels.remove(derivedLabel);
					
					NodeIterator ni = model.listObjectsOfProperty(r, RDFS.label);
					while (ni.hasNext()) {
						RDFNode node = ni.nextNode();
						if (node instanceof Literal) {
							String label = ((Literal) node).getString();
							
							labels.remove(label);
							
							removeLabelForTag(label, r);
						}
					}
					
					if (labels.size() == 1) {
						m_tagToLabels.put(r, labels.iterator().next());
					} else if (labels.size() > 0) {
						m_tagToLabels.put(r, labels);
					} else {
						m_tagToLabels.put(r, deriveTagLabel(r));
					}
				}
			}
		}
		
		// Update associations between objects and tags
		{
			Property tagProperty = model.createProperty(s_simile_tag);
			NodeIterator ni = model.listObjectsOfProperty(tagProperty);
			if (ni.hasNext()) {
				m_objectsToTags.clear();
				m_objectsToTagLabels.clear();
				m_labelToObjects.clear();
			}
			
			while (ni.hasNext()) {
				RDFNode node = ni.nextNode();
				if (node instanceof Literal) {
					continue;
				}
	
				Resource resource = (Resource) node;
				if (resource.isAnon()) {
					continue;
				}
				
				Set objects = (Set) m_tagToObjects.get(resource);
				if (objects != null) {
					ResIterator ri = model.listSubjectsWithProperty(tagProperty, resource);
					while (ri.hasNext()) {
						objects.remove(ri.nextResource());
					}
					// Caching strategy: set changes, create new set
					m_tagToObjects.put(resource, new HashSet(objects));
				}
	
				ResIterator ri = model.listSubjectsWithProperty(tagProperty, resource);
				while (ri.hasNext()) {
					Resource r = ri.nextResource();
					
					Set tags = (Set) m_objectToTags.get(r);
					if (tags != null) {
						tags.remove(resource);
						
						// Caching strategy: set changes, create new set
						m_objectToTags.put(r, new HashSet(tags));
					}
					m_objectToTagLabels.remove(r);
				}
			}
		}
	}
	
	protected void enterLabelForTag(String label, Resource tag) {
		Object o = m_labelToTags.get(label);
		if (o instanceof Set) {
			((Set) o).add(tag);
		} else if (o instanceof Resource) {
			Set tags = new HashSet();
			tags.add(o);
			tags.add(tag);
			
			m_labelToTags.put(label, tags);
		} else {
			m_labelToTags.put(label, tag);
			m_cachedTagLabels.add(label);
		}
	}
	
	protected void removeLabelForTag(String label, Resource tag) {
		Object o = m_labelToTags.get(label);
		if (o instanceof Set) {
			((Set) o).remove(tag);
		} else if (o instanceof Resource) {
			if (tag.equals(o)) {
				m_labelToTags.remove(label);
				m_cachedTagLabels.remove(label);
			}
		}
	}

	protected String deriveTagLabel(Resource tag) {
		String uri = tag.getURI();
		if (uri.startsWith(s_tagNamespace)) {
			String suffix = uri.substring(s_tagNamespace.length());
			int colon = suffix.indexOf(':');
			
			if (colon < 0) {
				return base64Decode(suffix);
			} else {
				return base64Decode(suffix.substring(0, colon));
			}
		} else {
			return uri;
		}
	}
	
	protected Set internalGetTags(Resource object) {
		Model model = m_profile.getModel();
		Property tagProperty = model.createProperty(s_simile_tag);
		
		Set tags = new HashSet();
		
		NodeIterator ni = model.listObjectsOfProperty(object, tagProperty);
		while (ni.hasNext()) {
			RDFNode node = ni.nextNode();
			if (node instanceof Literal) {
				continue;
			}
			
			Resource resource = (Resource) node;
			if (resource.isAnon()) {
				continue;
			}
			
			tags.add(resource);
		}
		
		return tags;
	}

	protected Set internalGetTagLabels(Resource object) {
		Model model = m_profile.getModel();
		Property tagProperty = model.createProperty(s_simile_tag);
		
		Set tags = getObjectTags(object);
		Set labels = new HashSet();
		
		Iterator i = tags.iterator();
		while (i.hasNext()) {
			Resource tag = (Resource) i.next();
			
			tagToLabels(tag, labels);
		}
		
		return labels;
	}

	protected Set internalGetObjects(Resource tag) {
		Model model = m_profile.getModel();
		Property tagProperty = model.createProperty(s_simile_tag);
		
		Set objects = new HashSet();
		
		ResIterator ri = model.listSubjectsWithProperty(tagProperty, tag);
		while (ri.hasNext()) {
			objects.add(ri.nextResource());
		}
		
		return objects;
	}
	
	protected void tagToLabels(Resource tag, Set results) {
		Object o = m_tagToLabels.get(tag);
		if (o instanceof Set) {
			results.addAll((Set) o);
		} else if (o instanceof String) {
			results.add(o);
		}
	}

	protected void labelToTags(String label, Set results, boolean create) {
		Object o = m_labelToTags.get(label);
		if (o instanceof Set) {
			results.addAll((Set) o);
		} else if (o instanceof Resource) {
			results.add(o);
		} else if (create) {
			String uri = s_tagNamespace + base64Encode(label) + ":" + s_tagSuffixHash;
			
			Resource tag = m_profile.getModel().createResource(uri);
			
			m_labelToTags.put(label, tag);
			m_tagToLabels.put(tag, label);
			
			m_cachedTagLabels.add(label);
			
			results.add(tag);
		}
	}

	static protected String md5hash(String s) {
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			byte[] bytes = s.getBytes();
			byte[] md5hash = md.digest(bytes);
			
			return bytesToString(md5hash);
		} catch (NoSuchAlgorithmException e) {
			return s;
		}
	}
	
	static protected String base64Encode(String s) {
		return new BASE64Encoder().encode(s.getBytes());
	}
	
	static protected String base64Decode(String s) {
		try {
			return new String(new BASE64Decoder().decodeBuffer(s));
		} catch (IOException e) {
			return s;
		}
	}
	
	static protected String bytesToString(byte[] bytes) {
		String s = "";
		for (int i = 0; i < bytes.length; i++) {
			String d = Integer.toString(i, 16);
			int length = d.length();
			
			if (d.length() < 2) {
				s += "0" + d;
			} else {
				s += d;
			}
		}
		return s;
	}
	
	static protected byte[] stringToBytes(String s) {
		byte[] bytes = new byte[s.length() / 2];
		
		for (int i = 0; i < bytes.length; i++) {
			String d = s.substring(i * 2, i * 2 + 2);
			
			bytes[i] = (byte) Byte.parseByte(d, 16);
		}
		return bytes;
	}
}
